﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Data;
using System.Globalization;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Dynamic;
using System.Net.Mail;
using System.Text;
using System.Security;
using System.Web;

namespace IntraVision.Web
{
    public delegate void LoginEvent(IUser user);

    public class LoginController<TDataContext,TUser> : ControllerBase
        where TDataContext : DataContext, new()
        where TUser : class, IUser, new()
    {
        protected string _UserKey = "user";
        protected string _UserSelectCommand = "Login=@0 and Password=@1 and !IsArchive";

        protected event LoginEvent _Authorized;

        // ключ перенаправления на начальную страницу - может меняться в зависимости от платформы клиента
        protected string RedirectFromLoginPageKey = "";

        public LoginEvent Authorized
        {
            get { return _Authorized; }
            set { _Authorized = value; }
        }

        public LoginController()
        {
            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["userKey"]))
                _UserKey = ConfigurationManager.AppSettings["userKey"];
        }

        public virtual void Default()
        {
            if (string.IsNullOrEmpty(LayoutPath) && LayoutPath != "none" && string.IsNullOrEmpty(Request["nolayout"]))
                LayoutPath = "~/views/layouts/page.haml";

            if (string.IsNullOrEmpty(TemplatePath))
                TemplatePath = "~/views/login.haml";

            if (string.IsNullOrEmpty(RedirectFromLoginPageKey))
                RedirectFromLoginPageKey = "default";

            string login = Request["login"];
            string password = Request["password"];

            bool rememberMe = !string.IsNullOrEmpty(Request["rememberme"]);
            bool viaCookie = false;

            string cookieKey = ConfigurationManager.AppSettings["authorizationCookieKey"];
            if (string.IsNullOrEmpty(cookieKey)) cookieKey = String.Format(CultureInfo.InvariantCulture, "AUTH_{0}", Request.Url.Host);

            //Если передан логин, сбросить пользователя
            if (!string.IsNullOrEmpty(Request["login"]))
                Session[_UserKey] = null;

            // пользователь в системе
            if (Session[_UserKey] != null)
            {
                RedirectFromLoginPage(Session[_UserKey] as IUser);
                return;
            }
            
            if (string.IsNullOrEmpty(login))
            {
                // авторизация через cookie
                if (Request.Cookies[cookieKey] != null)
                {
                    login = Server.UrlDecode(Request.Cookies[cookieKey].Values["login"]);
                    password = Server.UrlDecode(Request.Cookies[cookieKey].Values["password"]);
                    viaCookie = true;
                }
                //если куки не установлен, то авторизоваться через домен (если windows authentification включен)
                else if (ConfigurationManager.AppSettings["windowsAuthentication"] != null && ConfigurationManager.AppSettings["windowsAuthentication"].ToUpperInvariant() == "TRUE")
                {
                    AuthorizeViaIdentity();
                    return;
                }
            }

            ViewData["login"] = login;

            // попытка входа в систему с логином и паролем
            if (!string.IsNullOrEmpty(login) || !string.IsNullOrEmpty(password))
            {
                if (!viaCookie) password = RequestHelper.MD5Encode(password);

                TUser user = null;
                using (TDataContext dc = new TDataContext())
                {
                    user = dc.GetTable<TUser>().Where(_UserSelectCommand, login, password).FirstOrDefault();
                    if (user != null)
                    {
                        if (Authorized != null) Authorized(user);
                        Session[_UserKey] = user;
                        if (rememberMe)
                        {
                            HttpCookie cookie = new HttpCookie(cookieKey);

                            cookie.Values.Add("login", Server.UrlEncode(login));
                            cookie.Values.Add("password", Server.UrlEncode(password));

                            cookie.Expires = DateTime.Now.AddYears(1);

                            Response.SetCookie(cookie);
                        }
                        RedirectFromLoginPage(user as IUser);
                        return;
                    }
                    else
                    {
                        ViewData["message"] = "LOGIN_FAILED";
                    }
                }
            }

            RenderView();
        }

        public virtual void Logout()
        {
            Session[_UserKey] = null;
            string cookieKey = ConfigurationManager.AppSettings["authorizationCookieKey"];
            if (string.IsNullOrEmpty(cookieKey)) cookieKey = String.Format(CultureInfo.InvariantCulture, "AUTH_{0}", Request.Url.Host);
            if( Request.Cookies[cookieKey] != null ) Response.Cookies[cookieKey].Expires = DateTime.Now.AddDays(-1);

            Session.Clear();
            Session["logout"] = true;
            Response.Redirect(Request.ApplicationPath);
        }

        protected virtual void RedirectFromLoginPage(IUser user)
        {
            //Обратимся к ролям пользователя, чтобы закешировать их для дальнейшего использования
            user.HasPermission("cache");

            if (Session["RedirectAfterLogin"] != null)
            {
                string url = Session["RedirectAfterLogin"].ToString();
                Session["RedirectAfterLogin"] = null;
                Response.Redirect(url);
            }

            var urls = ConfigurationManager.GetSection("redirectAfterLogin") as NameValueCollection;
            if (urls == null || urls.Count == 0) { Response.Redirect(Request.ApplicationPath); return; }
            foreach (string key in urls.AllKeys)
            {
                if (user.HasPermission(key) || key == RedirectFromLoginPageKey)
                {
                    string url = URLHelper.GetRedirectUrl(urls[key], Request.ApplicationPath);
                    Response.Redirect(url ?? urls[key], false);
                    return;
                }
            }
            Response.Redirect(Request.ApplicationPath);
        }

        protected virtual void AuthorizeViaIdentity()
        {
            var login = Context.User.Identity.Name;
            TUser user = null;
            using (TDataContext dc = new TDataContext())
            {
                //При использовании windows-авторизации пользователь авторизуется по его windows-логину
                user = dc.GetTable<TUser>().Where("Login=@0", login).FirstOrDefault();
                if (user != null)
                {
                    if (Authorized != null) Authorized(user);
                    Session[_UserKey] = user;
                    RedirectFromLoginPage(user as IUser);
                }
                else
                    ViewData["message"] = "WINDOWS_AUTHENTICATION_FAILED";
            }
        }

        public virtual void RememberPassword()
        {
            LayoutPath = "~/views/layouts/page.haml";
            TemplatePath = "~/views/rememberpassword.haml";

            string email = Request["email"];

            ViewData["email"] = email;
            ViewData["message"] = "Введите адрес электронной почты, который Вы оставляли при регистрации, и нажмите кнопку \"Восстановить пароль\".";

            if (!string.IsNullOrEmpty(email))
            {
                using (var dc = new TDataContext())
                {
                    try
                    {
                        dc.Connection.Open();

                        var cmd = new SqlCommand("SELECT 1 FROM [User] WHERE Email=@Email", dc.Connection as SqlConnection);
                        cmd.Parameters.AddWithValue("@Email", email);
                        if (cmd.ExecuteScalar() != null)
                        {
                            string subject = ConfigurationManager.AppSettings["rememberpasswordsubject"] ?? "Восстановление пароля";
                            string body = ConfigurationManager.AppSettings["rememberpasswordbody"] ?? "Добрый день!%br% Для того, чтобы сбросить свой пароль перейдите по ссылке: %link%";
                            string link = string.Format(CultureInfo.InvariantCulture, "{0}login.ivp/resetpassword?email={1}&a={2}", ViewData["Base"], email, RequestHelper.MD5Encode(email));
                            link = "<a href=\"" + link + "\">" + link + "</a>";
                            body = body.Replace("%link%", link).Replace("%br%","<br/>");

                            cmd = new SqlCommand("INSERT dbo.Notification(Recipient, Subject, Body, InsertDate) VALUES(@Recipient, @Subject, @Body, @InsertDate)", dc.Connection as SqlConnection) { CommandType = CommandType.Text };
                            cmd.Parameters.Clear();
                            cmd.Parameters.AddWithValue("@Recipient", email);
                            cmd.Parameters.AddWithValue("@Subject", subject);
                            cmd.Parameters.AddWithValue("@Body", body);
                            cmd.Parameters.AddWithValue("@InsertDate", DateTime.Now);
                            cmd.ExecuteNonQuery();
                            
                            ViewData["message"] = "На указанный адрес электронной почты отправлена инструкция по смене пароля.";
                        }
                        else
                        {
                            ViewData["email"] = null;
                            ViewData["message"] = "Извините, но пользователь с таким адресом электронной почты не найден в базе данных.";
                        }
                    }
                    catch (Exception ex)
                    {
                        ViewData["message"] = "Не удалось восстановить пароль: " + ex.Message;
                    }
                }
            }
            RenderView();
        }

        public virtual void ResetPassword()
        {
            LayoutPath = "~/views/layouts/page.haml";
            TemplatePath = "~/views/rememberpassword.haml";

            string email = Request["email"];
            string md5 = Request["a"];
            
            ViewData["email"] = email;

            if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(md5))
            {
                RememberPassword();
                return;
            }

            if (RequestHelper.MD5Encode(email) != md5)
            {
                ViewData["message"] = "Код подтверждения неверный. Возможно, вы неправильно скопировали ссылку из письма. Попробуйте еще раз или заполните форму для восстановления пароля заново.";
                RenderView();
                return;
            }

            using (var dc = new TDataContext())
            {
                SqlTransaction tran = null;
                try
                {
                    var cn = dc.Connection as SqlConnection;
                    cn.Open();
                    tran = cn.BeginTransaction();
                    string password = Toolkit.PasswordGenerator.Generate(5);
                    var cmd = new SqlCommand("UPDATE [User] SET Password=@Password WHERE Email=@Email SELECT TOP 1 Login FROM [User] WHERE Email = @Email", cn) {Transaction = tran};
                    cmd.Parameters.AddWithValue("@Email", email);
                    cmd.Parameters.AddWithValue("@Password", RequestHelper.MD5Encode(password));

                    string login = Convert.ToString(cmd.ExecuteScalar(), CultureInfo.InvariantCulture);
                    if (string.IsNullOrEmpty(login)) throw new ArgumentException("Пользователь с таким адресом электронной почты не найден в базе данных.");

                    string subject = ConfigurationManager.AppSettings["resetpasswordsubject"] ?? "Восстановление пароля";
                    string body = ConfigurationManager.AppSettings["resetpasswordbody"] ?? "Добрый день! %br% Ваш пароль был сброшен. Для входа на сайт используйте логин: %login% и пароль: %password%";
                    string link = ViewData["Base"] + "login.ivp";
                    link = "<a href=\"" + link + "\">" + link + "</a>";

                    body = body.Replace("%link%", link);
                    body = body.Replace("%password%", password);
                    body = body.Replace("%login%", login);
                    body = body.Replace("%br%", "<br/>");

                    cmd = new SqlCommand("INSERT dbo.Notification(Recipient, Subject, Body, InsertDate) VALUES(@Recipient, @Subject, @Body, @InsertDate)", cn) { CommandType = CommandType.Text, Transaction = tran };
                    cmd.Parameters.Clear();
                    cmd.Parameters.AddWithValue("@Recipient", email);
                    cmd.Parameters.AddWithValue("@Subject", subject);
                    cmd.Parameters.AddWithValue("@Body", body);
                    cmd.Parameters.AddWithValue("@InsertDate", DateTime.Now);
                    cmd.ExecuteNonQuery();
                    tran.Commit();

                    ViewData["message"] = "Ваш новый пароль отправлен на адрес " + email + ".";
                }
                catch (Exception ex)
                {
                    if (tran != null) tran.Rollback();
                    ViewData["message"] = "Не удалось восстановить пароль: " + ex.Message;
                }
            }
            RenderView();
        }
    }
}
